
// ************************************
// * GStore - Inventory System - thomasfn *
// ************************************
// SERVERSIDE

module( "gstore", package.seeall )

local Items = {}
local Factories = {}

local BaseItem = {}
local BaseMeta = { __index = BaseItem }

function RegisterFactory( class, tbl )
	Factories[ class ] = tbl
end

function GetFactory( class )
	return Factories[ class ]
end

function LoadFactoryDirect( filename )
	_G.ITEM = {}
	setmetatable( ITEM, BaseMeta )
	ITEM.BaseClass = BaseItem
	include( filename )
	AddCSLuaFile( filename )
	if (ITEM.Base) then
		local base = GetFactory( ITEM.Base )
		if (base) then
			setmetatable( ITEM, { __index = base } )
			ITEM.BaseClass = base
		else
			ErrorNoHalt( "[GSTORE] Unable to derive '" .. ITEM.Classname .. "' from base '" .. ITEM.Base .. "'!\n" )
		end
	end
	RegisterFactory( ITEM.Classname, ITEM )
	Msg( "[GSTORE] Loaded item '" .. ITEM.Classname .. "'!\n" )
	ITEM = nil
end

function LoadFactoryFromTable( ITEM, nobase )
	if (!nobase) then
		setmetatable( ITEM, BaseMeta )
		ITEM.BaseClass = BaseItem
	end
	if (ITEM.Base) then
		local base = GetFactory( ITEM.Base )
		if (base) then
			setmetatable( ITEM, { __index = base } )
			ITEM.BaseClass = base
		else
			ErrorNoHalt( "[GSTORE] Unable to derive '" .. ITEM.Classname .. "' from base '" .. ITEM.Base .. "'!\n" )
		end
	end
	RegisterFactory( ITEM.Classname, ITEM )
	if (ITEM.Base) then
		Msg( "[GSTORE] Loaded item '" .. ITEM.Classname .. "' derived from '" .. ITEM.Base .. "'!\n" )
	else
		Msg( "[GSTORE] Loaded item '" .. ITEM.Classname .. "'!\n" )
	end
end

function LoadFactory( id, nobase )
	_G.ITEM = {}
	if (!nobase) then
		setmetatable( ITEM, BaseMeta )
		ITEM.BaseClass = BaseItem
	end
	include( "factories/" .. id .. ".lua" )
	AddCSLuaFile( "factories/" .. id .. ".lua" )
	if (ITEM.Base) then
		local base = GetFactory( ITEM.Base )
		if (base) then
			setmetatable( ITEM, { __index = base } )
			ITEM.BaseClass = base
		else
			ErrorNoHalt( "[GSTORE] Unable to derive '" .. id .. "' from base '" .. ITEM.Base .. "'!\n" )
		end
	end
	RegisterFactory( ITEM.Classname, ITEM )
	if (ITEM.Base) then
		Msg( "[GSTORE] Loaded item '" .. id .. "' derived from '" .. ITEM.Base .. "'!\n" )
	else
		Msg( "[GSTORE] Loaded item '" .. id .. "'!\n" )
	end
	ITEM = nil
end

function LoadBase( filename )
	_G.ITEM = {}
	include( filename )
	AddCSLuaFile( filename )
	table.Merge( BaseItem, ITEM )
	ITEM = nil
	Msg( "[GSTORE] Loaded base item!\n" )
end

function LoadAll( folder )
	for _, v in pairs( file.Find( "../" .. folder .. "/*.lua" ) ) do
		local id = string.Explode( ".", v )[1]
		LoadFactory( id, false )
	end
end

local nid = 1
function CreateItem( class, pos )
	local item = {}
	local factory = GetFactory( class )
	if (!factory) then return ErrorNoHalt( "Failed to create item '" .. class .. "' (does not exist)!\n" ) end
	setmetatable( item, { __index = factory } )
	item:Init( nid )
	Items[ nid ] = item
	nid = nid + 1
	item:ToWorld( pos or Vector( 0, 0, 0 ) )
	return item
end

function KillItem( item )
	local id = item._id
	Items[ id ] = nil
	item.Invalid = true
	/*for _, ply in pairs( player.GetAll() ) do
		ply:ItemRemove( item )
	end*/
	umsg.Start( "gs_itemnull" )
	umsg.Short( id )
	umsg.End()
end

function GetAllInRange( ply )
	local r = ItemSyncRange
	local i
	local its = {}
	for _, item in pairs( Items ) do
		if (item:GetPos():Distance( ply ) <= r) then
			table.insert( its, item )
		end
	end
end

function GetItem( id )
	return Items[ id ]
end

function GetItems()
	return Items
end

local function cmdPickup( ply, com, args )
	local item = Items[ tonumber( args[1] or "0" ) ]
	if (!item) then return Msg( "[GSTORE] Invalid item!\n" ) end
	local pos = item:GetPos() * 1 -- Make a copy
	local shootpos = ply:GetShootPos()
	pos.z = 0
	shootpos.z = 0
	local dist = pos:Distance( shootpos )
	if (dist > GSTORE_USERANGE) then return Msg( "[GSTORE] Too far!\n" ) end
	local zdist = item:GetPos().z - ply:GetShootPos().z
	if (zdist < 0) then zdist = -zdist end
	if (zdist > (2*GSTORE_USERANGE)) then return Msg( "[GSTORE] Too far (z)!\n" ) end
	local cti = ply:CanTakeItem_Reason( item )
	if (cti != "") then return ply:ChatPrint( cti ) end
	if (ply.PlayPickup) then ply:PlayPickup( ply:WorldToLocal( item:GetPos() ) ) end
	ply:Freeze( true )
	timer.Simple( 0.5, ply.GiveItem, ply, item )
	timer.Simple( 1, ply.Freeze, ply, false )
end
concommand.Add( "gstore_pickup", cmdPickup )

local function cmdDrop( ply, com, args )
	local item = Items[ tonumber( args[1] or "0" ) ]
	if (!item) then return Msg( "[GSTORE] Invalid item!\n" ) end
	if (!item.CanDrop) then return end
	if (ply:HasItem( item )) then ply:DropItem( item ) end
end
concommand.Add( "gstore_drop", cmdDrop )

local function cmdUse( ply, com, args )
	local item = Items[ tonumber( args[1] or "0" ) ]
	if (!item) then return Msg( "[GSTORE] Invalid item!\n" ) end
	if (!item.CanUse) then return end
	if (ply:HasItem( item )) then
		item:OnUse( ply )
		return
	end
	local pos = item:GetPos() * 1 -- Make a copy
	local shootpos = ply:GetShootPos()
	pos.z = 0
	shootpos.z = 0
	local dist = pos:Distance( shootpos )
	if (dist > GSTORE_USERANGE) then return Msg( "[GSTORE] Too far!\n" ) end
	local zdist = item:GetPos().z - ply:GetShootPos().z
	if (zdist < 0) then zdist = -zdist end
	if (zdist > (2*GSTORE_USERANGE)) then return Msg( "[GSTORE] Too far (z)!\n" ) end
	if (ply.PlayPickup) then ply:PlayPickup( ply:WorldToLocal( item:GetPos() ) ) end
	ply:Freeze( true )
	timer.Simple( 0.5, function()
		item:OnUse( ply )
	end	)
	timer.Simple( 1, ply.Freeze, ply, false )
end
concommand.Add( "gstore_use", cmdUse )

local function cmdCombine( ply, com, args )
	local itemA = Items[ tonumber( args[1] or "0" ) ]
	local itemB = Items[ tonumber( args[2] or "0" ) ]
	if (!itemA) then return Msg( "[GSTORE] Invalid item!\n" ) end
	if (!itemB) then return Msg( "[GSTORE] Invalid item!\n" ) end
	if (!ply:HasItem( itemA )) then return Msg( "[GSTORE] Player does not own item!\n" ) end
	if (!ply:HasItem( itemB )) then return Msg( "[GSTORE] Player does not own item!\n" ) end
	local b = itemA:Combine( ply, itemB )
	if (!b) then
		Msg( "[GSTORE] Combine failed!\n" )
		
	end
	if (ply.ActiveStory) then
		ply.ActiveStory:HandleEvent( ply, "combineitem", { Success=b, ItemA=itemA, ItemB=itemB } )
	end
end
concommand.Add( "gstore_combine", cmdCombine )

local function cmdEquip( ply, com, args )
	local item = Items[ tonumber( args[1] or "0" ) ]
	if (!item) then return Msg( "[GSTORE] Invalid item!\n" ) end
	if (!item.CanEquip) then return end
	if (item.Equipped) then return end
	if (ply:HasItem( item )) then item:Equip( ply, true ) end
end
concommand.Add( "gstore_equip", cmdEquip )

local function cmdUnequip( ply, com, args )
	local item = Items[ tonumber( args[1] or "0" ) ]
	if (!item) then return Msg( "[GSTORE] Invalid item!\n" ) end
	if (!item.CanEquip) then return end
	if (!item.Equipped) then return end
	if (ply:HasItem( item )) then item:Equip( ply, false ) end
end
concommand.Add( "gstore_unequip", cmdUnequip )

local function cmdDumpItems()
	for _, item in pairs( Items ) do
		print( tostring( item._id ) .. ": " .. item.Classname )
	end
end
concommand.Add( "gstore_debug_dumpitems_sv", cmdDumpItems )

local function cmdDumpItemInfo( ply, com, args )
	local item = Items[ tonumber( args[1] or "0" ) ]
	if (!item) then return print( "[GSTORE] Invalid item!" ) end
	print( "[" .. tostring( item._id ) .. "][" .. item.Classname .. "]: " .. tostring( item:GetPos() ) ..
		", " .. ((item.InWorld and "In World") or "Not in World") .. ", " .. (item.Container and ("Container [" ..
		tostring( item.Container._id ) .. "]") or "Not in Container") );
end
concommand.Add( "gstore_debug_dumpiteminfo_sv", cmdDumpItemInfo )

local nextTick = 0
local function Tick()
	if (CurTime() >= nextTick) then
		nextTick = CurTime() + (1/GSTORE_TICKRATE)
		for _, item in pairs( GetItems() ) do
			item:Tick()
		end
		for _, ply in pairs( player.GetAll() ) do
			ply:SyncGStore()
		end
	end
end
hook.Add( "Tick", "gstore.tick", Tick )

local mPly = _R.Player

function mPly:SyncGStore()
	if (!self.GStore) then self.GStore = {} end
	for _, item in pairs( gstore.GetItems() ) do
		if (item:ShouldSync( self )) then item:Sync( self ) end
	end
end